#include "pattern.h"
#include "node.h"
#include "charclass.h"

#include <iostream>
#include <cstdlib>
using namespace std;
Pattern::Pattern(const char *_pattern, int _flags){
    pattern = _pattern;
    flags = _flags;
    root = 0;

    accept_node = new Accept(this);
    capture_group_count = 0;
    compile();
}

Pattern::~Pattern(){
    for(size_t j=0;j<child_nodes.size();++j){
        delete child_nodes[j];
    }
}


void Pattern::compile(){
    if(root)
        return;
    i=0;
    pattern_len = strlen(pattern);
    root = build();
}

Matcher* Pattern::match(const string& text){
    Matcher* m = new Matcher(this,text);
    if(root->match(m,0,text))
        return m;
    else {
        delete m;
        return 0;
    }
}

Matcher* Pattern::search(const string& text,size_t from){
    Matcher* m = new Matcher(this,text);
    do{
        if(root->match(m,from,text)){
            m->setFrom(from);
            return m;
        }
        ++from;
    }while(from<text.length());

    delete m;
    return 0;
}

Node* Pattern::build(){

    Node* head = 0;
    Node* choice=0;
    while(!atEnd()){
        choice = readAlternation();
        if(!head){
            head = choice;
        } else {
            Alternative* branch = dynamic_cast<Alternative*>(head);
            if(branch){
                branch->appendChoice(choice);
            } else {
                head = new Alternative(this,head,choice);
            }
        }
        if(peek()!='|')
            break;
        next();
    }
    return head;
}

Node* Pattern::readAlternation(){
    Node* head = 0;
    Node* cur_tail=0;
    Node* node = 0;
    int ch;
    while(!atEnd()){
        ch = next();
        switch(ch){
        case '|':
            unread();
            return head;
        case '+':
        case '*':
        case '?':
        case '{':
            throw PatternSyntaxError("multiple repeat");
            break;
        case '[':
            node = readCharClass();
            break;
        case '(':
            node = readGroup();
            break;
        case ')':
            //end of group
            return head;
        case '.':
            if(hasFlag(Pattern::DOTALL))
                node = new DotAll(this);
            else
                node = new Dot(this);
            break;
        case '^':
            if(hasFlag(Pattern::MULTILINE))
                node = new LineBegin(this);
            else
                node = new StringBegin(this);
            break;
        case '$':
            if(hasFlag(Pattern::MULTILINE))
                node = new LineEnd(this);
            else
                node = new StringEnd(this);
            break;
        case '\\':
            ch = readEscape(&node);
            if(ch != -1){
                if(hasFlag(Pattern::IGNORECASE)){
                    node = new IgnoreCaseLiteral(this,ch);
                } else {
                    node = new Literal(this,ch);
                }
            }
            break;        
        default:
            if(hasFlag(Pattern::IGNORECASE)){
                node = new IgnoreCaseLiteral(this,ch);
            } else {
                node = new Literal(this,ch);
            }
        }
        if(node){
            node = closure(node);
            if(cur_tail){
                cur_tail->next = node;
                cur_tail = node;
            } else {
                head = cur_tail = node;
            }
        }
    }
    return head;
}

// return -1 if node_ref is assigned a node
int Pattern::readEscape(Node **node_ref){
    int ch = next();

    switch(ch){
    case '[':
    case '(':
    case '{':
    case '$':
    case '^':
    case '.':
    case '?':
    case '+':
    case '|':
        return ch;
   // case '0': don't know
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        ch = ch-'0';
        if(isdigit(peek()))
            ch = ch*10 + next()-'0';
        (*node_ref) = new GroupRef(this,ch);
        return -1;
    case 'A':
        (*node_ref) = new StringBegin(this);
        return -1;
    case 'b':
        (*node_ref) = new Boundary(this);
        return -1;
    case 'd':
        (*node_ref) = CharClass::digit(this);
        return -1;
    case 'D':
        (*node_ref) = CharClass::digit(this)->getComplement();
        return -1;
    case 's':
        (*node_ref) = CharClass::space(this);
        return -1;
    case 'S':
        (*node_ref) = CharClass::space(this)->getComplement();
        return -1;
    case 'w':
        (*node_ref) = CharClass::word(this);
        return -1;
    case 'W':
        (*node_ref) = CharClass::word(this)->getComplement();
        return -1;
    case 'Z':
        (*node_ref) = new StringEnd(this);
        return -1;
    default:
        throw UnsupportedEscape("");
    }
}

CharClass* Pattern::readCharClass(bool /*consume*/){
    bool first = true;
    CharClass* result = 0;
    CharClass* node = 0;
    bool include = true;

    while(!atEnd()){
        char ch = next();
        if(ch=='^'){
            if(first){
                include = false;
                continue; //else treat as literal
            }
        } else if(ch=='[') {
            first = false;
            node = readCharClass();
            if(result)
                result = result->getUnion(node);
            else
                result = node;
            continue;
        } else if(ch=='&') {
            if(peek()=='&'){
                if(result==0)
                    throw PatternSyntaxError("char class before '&&' empty");
                next();next(); // need a [
                CharClass* right = readCharClass();
                result = result->getIntersecion(right);
                continue;
            } //else treat as literal
        } else if(ch==']'){
            break; // end
        }
        first = false;
        if(peek()=='-'){
            next();
            if(peek()!=']'){ // x-y
                char end_range = next();
                if(hasFlag(IGNORECASE))
                    node = new IgnoreCaseRangeCharClass(this,ch,end_range);
                else
                    node = new RangeCharClass(this,ch,end_range);

                if(result)
                    result = result->getUnion(node);
                else
                    result = node;
                continue;
            } else {
                unread(); // x-],treat as literal
            }
        }
        CharClass* cc;
        if(hasFlag(IGNORECASE))
            cc = new IgnoreCaseSingleCharClass(this,ch);
        else
            cc = new SingleCharClass(this,ch);
        if(result)
            result = result->getUnion(cc);
        else
            result = cc;
    }
    if(!include)
        return result->getComplement();
    else
        return result;
}

Node* Pattern::closure(Node *node){
    Node* result=node;

    if(atEnd())
        return node;
    char ch = peek();
    if(ch=='?'){
        next();
        if(!atEnd() && peek()=='?')
            result = new Que(this,node,false);
        else
            result = new Que(this,node,true);
    } else if(ch=='*' || ch=='+'){
        next();
        const int REPETITION_MAX = 0x7fffffff;
        if(!atEnd() && peek()=='?'){
            next();
            if(ch == '*')
                result = new Repetition(this,node,0,REPETITION_MAX,false);
            else
                result = new Repetition(this,node,1,REPETITION_MAX,false);
        } else {            
            if(ch == '*')
                result = new Repetition(this,node,0,REPETITION_MAX,true);
            else
                result = new Repetition(this,node,1,REPETITION_MAX,true);
        }
    } else if(ch=='{'){
        next();
        char temp;
        int min=0;
        while(isdigit(temp=next())){
            min = min*10 + temp-'0';
        }
        int max=0x7fffffff;
        if(temp=='}'){ //{m}
            max = min;
        } else if(temp==',' && isdigit(peek())){ //{m,n}
            max=0;
            while(isdigit(temp=next())){
                max = max*10 + temp-'0';
            }
        } else {//{m,}
            next();
        }
        if(!atEnd() && peek()=='?'){
            next();
            result = new Repetition(this,node,min,max,false);
        } else {
            result = new Repetition(this,node,min,max,true);
        }
    }
    return result;
}

Node* Pattern::readGroup(){
    Node* body=0;

    if(peek()=='?'){
        next();
        char ch = next();
        if(ch==':'){ // (?:
            body = build();
            --capture_group_count;
            return new Group(this,body,false);
        } else if(ch=='('){ // (?(id/name)yes|no)
            string t;
            bool all_digit = true;
            while((ch=next())!=')'){
                t.push_back(ch);
                all_digit &= isdigit(ch);
            }
            int ref_group_index;
            if(all_digit){
                ref_group_index = atoi(t.c_str());
            } else {
                ref_group_index = getIndexByName(t);
            }
            Node* yes_pattern = readAlternation();
            Node* no_pattern = 0;
            if(!atEnd() && peek()=='|'){
                next();
                no_pattern = readAlternation();
            }
            if(!no_pattern)
                no_pattern = getAcceptNode();
            return new YesNoBranch(this,ref_group_index,yes_pattern,no_pattern);
        } else if(ch=='P'){
            ch = next();
            if(ch=='<'){ // (?P<name>
                string group_name;
                while((ch=next())!='>'){
                    group_name.push_back(ch);
                }
                ++capture_group_count;
                int index = capture_group_count; // save current index
                body = build();
                return new Group(this,body,true,index,group_name);
            } else if(ch=='='){ // (?P=
                //ref by name
                string ref_name;
                while((ch=next())!=')'){
                    ref_name.push_back(ch);
                }
                return new GroupRef(this,ref_name);
            }
        } else if(ch=='#'){
            //(?# comment
            while((ch=next())!=')') {}
            return 0;
        } else if(ch=='=' || ch=='!'){ //(?= (?! zero-width lookhead assertion
            body = build();
            if(ch=='=')
                return new PosLookahead(this,body);
            else
                return new NegLookahead(this,body);
        } else if(ch=='<'){ //zero-width lookbehind assertion
            //TODO
            return 0;
        }
    } else {
        ++capture_group_count;
        int index = capture_group_count; //save current
        body = build();
        return new Group(this,body,true,index);
    }    
}

